#include "../DEHT/deht.h"
#include "../global/global_rainbow_table.h"
#include "../global/global_auth.h"
#include <stdlib.h>
#include "../global/global_declarations.h"
#include "../exhaustive_search/rule.h"
#include "../exhaustive_search/dictionary.h"

/* The algorithm described in the project specification was something like this:
 * given a hash, guess where it is in the hash chain, calculate the end of the chain
 * and then *query the DEHT* for this value.
 *
 * However, since collisions (i.e chains that collide and thus end in the same value) are
 * actually quite common, replacing query_DEHT (which can only retrieve one value per key)
 * with a function that will retrieve *ALL* the values for the key, will greatly
 * improve the hit ratio of crack_using_rainbow_table.
 *
 * hence the function retrieve_multiple_values_from_DEHT.
 */


bool look_for_value_in_chain(char* to_crack_hash, char* chain_beginning_hash, int chain_length, int64* seed_arr,
		BasicHashFunctionPtr hash_func, int hash_length, String* /* out */ dest) {
	int i;
	int64 temp_pass;
	String* str_pass = make_empty_string();
	bool ret_val = false;

	unsigned char* cur_hash = calloc((hash_length + 1), sizeof(unsigned char*));
	memcpy((char*) cur_hash, chain_beginning_hash, hash_length);
	/* calculate the chain, looking for the given hash */
	for (i = 0; i < chain_length; ++i) {
		temp_pass = pseudo_random_function(cur_hash, hash_length, seed_arr[i]); /* reduction */

		int64_to_word(temp_pass, str_pass);
		hash_func((unsigned char*) str_pass->data, str_pass->size,
				(unsigned char*) cur_hash);

		/* compare current hash to to_crack_hash */
		if (memcmp(cur_hash, to_crack_hash, hash_length) == 0) {
			String_copy_string(dest, str_pass);
			ret_val = true;
			break;
		}



	}
	String_free(str_pass);
	free(cur_hash);
	return ret_val;
}



void handle_hex_line(char* line, DEHT* d, IniFileContent* content) {
	int i, j, k;
	unsigned char key[20];
	String* result;
	String* input;
	int hash_length = hash_Length(d->header.sDictionaryName);
	String* results;
	int results_len;
	unsigned char hash_bin[20];


	result = make_empty_string();
	input = make_empty_string();
	hexa2binary(line, hash_bin, hash_length);
	String_init(input, hash_length);
	memcpy(input->data, hash_bin, hash_length);
	input->size = hash_length;
	for (i = 0; i < content->rainbow_chain_length; i++) {
		/* guess that the given input is in place rainbow_chain_length - i in the chain */
		do_chain_hashing(content->rainbow_chain_length, input, d->seeds, which_hash(d->header.sDictionaryName),
				hash_length, key, false, content->rainbow_chain_length - i);


		/* retrieve ALL values from the DEHT with that key */
		retrieve_multiple_values_from_DEHT(d, key, hash_length, &results, &results_len);

		if (results_len == 0) {
			continue;
		}

		for (j = 0; j < results_len; j++) {
			/* for each password whose end of chain is the same as that calculated before,
			 * calculate the entire chain and look for the target hash in it.
			 */
			bool value_found;
			unsigned char to_crack_hash[20] = {0};
			which_hash(d->header.sDictionaryName)((unsigned char*)results[j].data, results[j].size, to_crack_hash);

			value_found = look_for_value_in_chain(input->data, (char*)to_crack_hash, content->rainbow_chain_length, d->seeds,
					which_hash(d->header.sDictionaryName), hash_Length(d->header.sDictionaryName), result);

			if (value_found) {
				int k;
				printf("Try to login with password \"%s\"\n", result->data);
				for (k = 0; k < results_len; k++) {
					free(results[k].data);
				}
				free(results);
				results = 0;
				String_free(result);
				String_free(input);
				return;
			}
		}

		for (k = 0; k < results_len; k++) {
			free(results[k].data);
		}
		free(results);
		results = 0;
	}
	printf("Sorry but this hash doesn't appears in pre-processing\n");

	String_free(result);
	String_free(input);
}

void main_loop(DEHT* d, IniFileContent* content) {
	char line[128];

	printf(">> ");
	while (fgets(line, sizeof(line), stdin) != 0) {
		if (line[strlen(line)-1] == '\n') {
			line[strlen(line)-1] = '\0';
		}
		if (strcmp(line, "quit") == 0) {
			return;
		}

		else if (validate_hex_string(line)) {
			handle_hex_line(line, d, content);
		}

		else if (line[0] == '!') {
			unsigned char hash[20];
			char hash_hex[41] = {0};
			which_hash(d->header.sDictionaryName)((unsigned char*)&line[0] + 1, strlen(line) - 1, (unsigned char*)hash);
			binary2hexa(hash, hash_Length(d->header.sDictionaryName), hash_hex, 40);
			printf("\tIn hexa password is%s\n", hash_hex);
			handle_hex_line((char*)hash_hex, d, content);
		}

		else {
			fprintf(stderr, "Commands are either hexa, !password or quit.\n");
		}

		printf(">> ");
	}
}


int main(int argc, char* argv[]) {
	IniFileContent content;
	bool retval;
	DEHT* d;
	String* rule;
	char* prefix, *ini_filename;
	if (argc != 3) {
		fprintf(stderr, "Error: Usage carck_using_rainbow_table <filenames prefix> <preferences filename>\n");
		exit(1);
	}


	prefix = argv[1];
	ini_filename = argv[2];


	retval = read_ini_file(&content, ini_filename);
	if (!retval) {
		perror(ini_filename);
	}

	d = load_DEHT_from_files(prefix, str_mod, keyTo64Bit);

	if (d == 0) {
		perror(0);
		return 1;
	}
	read_DEHT_pointers_table(d);

	rule = make_string_from_char_arr(content.rule);
	the_rule = make_rule_list_from_string(*rule);
	the_dictionary = create_dictionary_from_file(content.dictionary_filename);

	main_loop(d, &content);

	lock_DEHT_files(d);
	free_rule(the_rule);
	free_dictionary(the_dictionary);
	String_free(rule);
	free(d);

	return 0;
}
